package roborally.model.items;

import roborally.model.Energy;
import roborally.model.IEnergyHolder;
import roborally.model.Item;
import roborally.model.Robot;
import exception.IsTerminatedException;
import be.kuleuven.cs.som.annotate.*;
import be.kuleuven.cs.som.taglet.*;

/**
 * A class of Batteries. Batteries hold energy, have a certain weight. They can transfer energy to another energyHolder.
 * 
 * @author Mathieu REYMOND
 * @author Joren VERSPEURT
 * 
 * @version 2.0
 *
 *@invar ...
 *		 | this.canHaveAsEnergy(getEnergy())
 */
public class Battery extends Item implements IEnergyHolder {

	/**
	 * Create a battery with a given energy amount and a given weight.
	 * The default maximum energy amount is 5000 ws
	 * 
	 * @param energyAmount The battery's given energy
	 * @param weight The weight of the battery
	 * 
	 * @effect ...
	 * 		   | this(energyAmount, 5000, weight)
	 */
	public Battery(double energyAmount, int weight) {
		
		this(energyAmount, 5000, weight);
		
	}
	
	/**
	 * Create a battery with a given energy, a maximum amount of energy and  given weight.
	 * 
	 * @param energyAmount The battery's given energy
	 * @param maxEnergyAmount The maximum amount of energy this battery may contain
	 * @param weight The weight of the battery
	 * 
	 * @pre ...
	 * 		| new.canHaveAsEnergy(energyAmount)
	 * 
	 * @post ...
	 * 		 | new.getEnergy() == energyAmount
	 * @post ...
	 * 		 | new.getMaxEnergy() == maxEnergyAmount
	 */
	public Battery(double energyAmount, double maxEnergyAmount, int weight) {
		
		super(weight);
		this.energyAmount = new Energy(energyAmount);
		this.maxEnergyAmount = new Energy(maxEnergyAmount);
		
	}
	
	/**
	 * check if the given energy amount is valid for this battery
	 * 
	 * @param ws The given energy
	 * @return ws >= 0 && ws <= getMaxEnergy()
	 */
	public boolean canHaveAsEnergy(Energy ws) {
		
		return ws.getValueIn(Energy.Unit.WS) >= 0 && ws.getValueIn(Energy.Unit.WS) <= getMaxEnergy().getValueIn(Energy.Unit.WS);
	}
	
	/**
	 * Get the battery's current energy.
	 */
	@Override @Basic
	public Energy getEnergy() {
		
		return energyAmount;
	}
	
	/**
	 * Get The battery's max energy amount.
	 */
	@Basic
	public Energy getMaxEnergy() {
		return maxEnergyAmount;
	}
	
	/**
	 * The battery's energy amount.
	 */
	private Energy energyAmount;

	/**
	 * The battery's maximum energy amount.
	 */
	private final Energy maxEnergyAmount;

	/**
	 * Get the amount of energy the battery is recharged when it is hit by a laser.
	 */
	@Basic
	private Energy getLaserCharge() {
		return LASER_CHARGE;
	}

	/**
	 * The amount of energy the battery is recharged when it is hit by a laser.
	 */
	private static final Energy LASER_CHARGE = new Energy(500);

	/**
	 * Recharge the battery by the given amount.
	 * 
	 * @param amount The amount of energy that will be added to the battery
	 * 
	 */
	@Override
	public void recharge(Energy amount) {
		
		energyAmount = new Energy(getEnergy().getValueIn(Energy.Unit.WS) + amount.getValueIn(Energy.Unit.WS));
	}

	/**
	 * Transfer energy from this battery to another energyHolder.
	 * 
	 * @param other The energyHolder that will receive energy from this battery
	 * @param amount The amount of energy that will be transfered
	 * 
	 */
	@Override
	public void transferEnergy(IEnergyHolder other, Energy amount) throws NullPointerException {
		
		energyAmount = new Energy(getEnergy().getValueIn(Energy.Unit.WS) - amount.getValueIn(Energy.Unit.WS));
		other.recharge(amount);

	}

	
	/**
	 * A Robot will use this battery and transfer energy to it.
	 * 
	 * @param robot The robot that will use this battery
	 * 
	 * @throws IllegalArgumentException
	 * 		   | !robot.getItems().contains(this)
	 * @throws IsTerminatedException
	 * 		   | robot.isTerminated()
	 * 
	 * @effect ...
	 * 		   | this.transferEnergy(robot, robot.getMaxEnergy() - robot.getEnergy() > this.getEnergy()? this.getEnergy() : robot.getMaxEnergy() - robot.getEnergy())
	 * @post ...
	 * 		   | if new.getEnergy() == 0
	 * 		   | then new.isTerminated()
	 * 		   | else !new.isTerminated()
	 */
	public void use(Robot robot) throws IllegalArgumentException, IsTerminatedException {
		if(robot.isTerminated()) throw new IsTerminatedException("Robot is terminated");
		if(!robot.getItems().contains(this)) throw new IllegalArgumentException("This item is not a possession of this robot");
		
		double transferAmount = robot.getCurrentMaxEnergy().getValueIn(Energy.Unit.WS) - robot.getEnergy().getValueIn(Energy.Unit.WS);
		
		this.transferEnergy(robot, transferAmount > this.getEnergy().getValueIn(Energy.Unit.WS)? this.getEnergy():new Energy(transferAmount));
		
		if(this.getEnergy().getValueIn(Energy.Unit.WS) == 0) {
			this.terminate();
		}

	}
	

	/**
	 * Invoked when the battery is shot by a laser.
	 * If a battery is shot by a laser, it's amount of energy is augmented.
	 * 
	 * @effect When a battery is shot by a laser, it's amount of energy is augmented. If this new energy exceeds it's maximum amount,
	 * 		   set it's energy to it's maximum amount.
	 * 		   | if(getEnergy()+getLaserCharge() > getMaxEnergy())
	 * 		   | then recharge(getMaxEnergy()-getEnergy())
	 * 		   | else recharge(getLaserCharge())
	 */
	@Override
	public void takeShot() {
		if(getEnergy().getValueIn(Energy.Unit.WS)+getLaserCharge().getValueIn(Energy.Unit.WS) > getMaxEnergy().getValueIn(Energy.Unit.WS)){
			recharge(new Energy(getMaxEnergy().getValueIn(Energy.Unit.WS)-getEnergy().getValueIn(Energy.Unit.WS)));	
		}
		else{
			recharge(getLaserCharge());
		}
	}
	
	/**
	 * Clone this battery
	 * 
	 * @return ...
	 * 		   | (result.getBoard() == this.getBoard() && result.getPosition() == this.getPosition())
	 * @return ...
	 * 		   | result.equals(this(getEnergy(), getMaxEnergy(), getWeight()))
	 */
	@Override
	public Battery clone() {
		
		Battery newBattery = new Battery(getEnergy().getValueIn(Energy.Unit.WS), getMaxEnergy().getValueIn(Energy.Unit.WS), getWeight());
		newBattery.setBoardPosition(getBoard(), getPosition());
		
		return newBattery;
	}

	/**
	 * Return a String describing this battery.
	 * 
	 * @return ...
	 * 		   | result == "B"+ getPosition() +" ( " + getEnergy() + "/" + getMaxEnergy() + " )"
	 */
	@Override
	public String toString() {
		
		return "B"+ getPosition() +" ( " + getEnergy() + "/" + getMaxEnergy() + " )";
	}

}
